#include <errno.h>
#include <math.h>
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <unistd.h>
#include <ctype.h>
#include <getopt.h>
#include <assert.h>
#include <SDL/SDL.h>
#include <SDL/SDL_image.h>
#include <SDL/SDL_mixer.h>

#define FONTNAME "FONT.png"
#define LOGONAME "LOGO.png"
#define MUSIC "MODULE.mod"
#define LOGO_SLOW 0.129804823767969
#define LOGO_FAST 0.317012059419922

typedef struct Scroller_t {
  short scrollerwidth; //Width of scroller in characters
  char *text;
  int x, y;
  double speed;
  size_t textlen;
  Uint32 startticks;
} Scroller_t;


struct Copperbar {
  const char *fname;
  int distance;
  double speed;
  double position;
  SDL_Surface *surf;
} copperbars[] = {
  { "copper_bar.18.png", 107, -19.0/17.0 },
  { "copper_bar.20.png", 127,  29.0/17.0 },
  { "copper_bar.28.png", 139, -41.0/17.0 },
  { "copper_bar.30.png", 157,  53.0/17.0 },
  { NULL }
};

SDL_Surface *screen;
SDL_Surface *font;
SDL_Surface *logo;
SDL_Surface *black_void;
Mix_Music *music;
Uint32 last_ticks;
double movementrate;

int hide_scroller_text = 0;
int windowed_mode = 1;
const int fontcolumns = 10;
const int fontwidth = 32;
const int fontheight = 32;


int init_bars(struct Copperbar *copperbars) {
  struct Copperbar *barptr;
  SDL_Surface *surf;

  for(barptr = copperbars; barptr->fname != NULL; ++barptr) {
    surf = IMG_Load(barptr->fname);
    printf("%p loading '%s' = %p...\n", barptr, barptr->fname, surf);
    if(!surf) return -1;
    barptr->position = 0;
    barptr->surf = surf;
  }
  return 0;
}


void display_bars(struct Copperbar *barptr, double movementrate) {
  SDL_Rect dest = { 0, 0, 0, 0 };
  int x;

  for(; barptr->fname != NULL; ++barptr) {
    barptr->position += movementrate * barptr->speed;
    if(barptr->speed < 0) {
      if(barptr->position < -barptr->surf->w) barptr->position += barptr->distance;
    } else {
      if(barptr->position > barptr->distance - barptr->surf->w) barptr->position -= barptr->distance;
    }
    x = barptr->position;
    while(x < screen->w) {
      dest.x = x;
      SDL_BlitSurface(barptr->surf, NULL, screen, &dest);
      x += barptr->distance;
    }
  }
}


void print_text(int x, int y, int n, const char *txt) {
  int fontrow, fontcol;
  int ch;

  SDL_Rect src = {
    0, 0, fontwidth, fontheight
  };
  SDL_Rect dest = {
    x, y, 0, 0
  };

  for(; n && *txt; --n, ++txt) {
    ch = *txt - ' ';
    if(ch >= 0) {
      fontrow = ch / fontcolumns;
      fontcol = ch % fontcolumns;
      src.x = fontcol * fontwidth;
      src.y = fontrow * fontwidth;
      SDL_BlitSurface(font, &src, screen, &dest);
      x += fontwidth;
      if(x > 0 && x > screen->w) break;
      dest.x = x;
    }
  }
}


const char *scroll(Scroller_t *scrollerptr, Uint32 now) {
  double delta = -scrollerptr->startticks;
  int x;
  size_t charpos;
  const char *charptr = NULL;

  delta += now;
  delta /= scrollerptr->speed;

  x = (now - scrollerptr->startticks) / scrollerptr->speed;
  charpos = x / fontwidth;
  if(charpos > scrollerptr->textlen) {
    scrollerptr->startticks = now;
  } else {
    x = scrollerptr->x - x % fontwidth;
    charptr = scrollerptr->text + charpos;
    print_text(x, scrollerptr->y, 26, charptr);
  }
  return charptr == NULL ? charptr : charptr + scrollerptr->scrollerwidth;
}


void handlesignal(int signal) {
  SDL_Quit();
  exit(128 + signal);
}


static struct option long_options[] = {
  { "fullscreen", no_argument, NULL, 'f' },
  { NULL }
};


void load_scroller_text(Scroller_t *scroller) {
  char buf[0x2000];
  FILE *in;
  size_t bytesread;
  char *sptr;
  const int nums = scroller->scrollerwidth + 2; //Number of spaces

  in = fopen("TEXT", "r");
  if(in) {
    memset(buf, ' ', sizeof(buf));
    bytesread = fread(buf + nums, 1, sizeof(buf) - nums - 1, in);
    if(bytesread > 0) {
      buf[nums + bytesread] = 0;
      sptr = malloc(strlen(buf) + scroller->scrollerwidth + 1);
      if(sptr) {
	scroller->text = sptr;
	strcpy(scroller->text, buf);
	printf("scroller=%p sptr=%p, strlen=$%04X\n", scroller, sptr, (unsigned)strlen(sptr));
      }
    }
    fclose(in);
  }
}


int main(int argc, char **argv) {
  SDL_Event event;
  SDL_Rect dest;
  SDL_Surface *tempsurface;
  int opt;
  double x, y;
  const char *scroll_char;
  int last_option_char = -1;
  int option_index = 0;
  int running = 1;
  int fullscreen = 0;
  double logo_amplitude = 0;
  double logo_tau = 0;
  double logo_speed_y = LOGO_SLOW;
  double fade_out = -1;
  Scroller_t scroller = {
    25,
    "                             NO SCROLLER TEXT COULD BE LOADED! :-(",
    0, 400, 15.71927
  };

  while ((opt = getopt_long(argc, argv, "f", long_options, &option_index)) != -1) {
    switch(opt) {
    case 'f':
      fullscreen = 1;
      break;
    default:
      fprintf(stderr, "getopt returned: $%02x '%c'\n", opt, isprint(opt) ? opt : '?');
      return -1;
    }
  }
  if(SDL_Init(SDL_INIT_EVERYTHING) != 0) goto sdl_error;
  signal(SIGINT, handlesignal);
  signal(SIGTERM, handlesignal);
  signal(SIGQUIT, handlesignal);
  atexit(SDL_Quit);
  if((screen = SDL_SetVideoMode(800, 480, 32, (!fullscreen ? 0 : SDL_FULLSCREEN) | SDL_DOUBLEBUF | SDL_HWSURFACE)) == NULL) goto sdl_error;
  if((tempsurface = SDL_CreateRGBSurface(0, screen->w, screen->h, 8, 0, 0, 0, 0)) == NULL) goto sdl_error; else {
    black_void = SDL_DisplayFormatAlpha(tempsurface);
    if(black_void == NULL) goto sdl_error;
    SDL_FreeSurface(tempsurface);
  }
  if((font = IMG_Load(FONTNAME)) == NULL) {
    fprintf(stderr, "IMG Error: %s\n", IMG_GetError());
    return 2;
  }
  if((logo = IMG_Load(LOGONAME)) == NULL) {
    fprintf(stderr, "IMG Error: %s\n", IMG_GetError());
    return 2;
  }
  SDL_ShowCursor(SDL_DISABLE);
  if(Mix_OpenAudio(MIX_DEFAULT_FREQUENCY, AUDIO_S16, MIX_DEFAULT_CHANNELS, 1024) != 0) {
    return 5;
  }
  if((music = Mix_LoadMUS(MUSIC)) != NULL) {
    Mix_FadeInMusic(music, -1, 2720);
  } else {
    print_text(0, 0, 30, "NO MUSIC!");
    SDL_Flip(screen);
    SDL_Delay(650);
  }
  if(init_bars(copperbars) == -1) {
    print_text(0, 0, 30, "COPPER BARS FAILED!");
    SDL_Flip(screen);
    SDL_Delay(650);
  }
  load_scroller_text(&scroller);
  last_ticks = SDL_GetTicks();
  scroller.textlen = strlen(scroller.text);
  scroller.startticks = last_ticks;
  while(running) {
    Uint32 ticks = SDL_GetTicks();
    movementrate = (ticks - last_ticks) / 50.0;

    y = 400 + sinf(ticks / 382.0) * 30;
    SDL_FillRect(screen, NULL, SDL_MapRGB(screen->format, 0, 0, 1)); 
    scroller.y = y;
    if(hide_scroller_text) scroll_char = scroll(&scroller, ticks);
    display_bars(copperbars, movementrate);
    if(!hide_scroller_text) scroll_char = scroll(&scroller, ticks);
    if(scroll_char != NULL) {
      if(islower(*scroll_char) && *scroll_char != last_option_char) {
	last_option_char = *scroll_char;
      }
      switch(last_option_char) {
      case 'l':
	if(logo_amplitude < 43) logo_amplitude += .83331;
	break;
      case 'o':
	if(logo_amplitude > 0) {
	  logo_amplitude -= .911121;
	  if(logo_amplitude < 0) logo_amplitude = 0;
	}
	break;
      case 'f':
	if(logo_speed_y < LOGO_FAST) {
	  logo_speed_y *= 1.1;
	  if(logo_speed_y > LOGO_FAST) {
	    last_option_char = 0;
	    logo_speed_y = LOGO_FAST;
	  }
	}
	break;
      case 's':
	if(logo_speed_y > LOGO_SLOW) {
	  logo_speed_y /= 1.1;
	  if(logo_speed_y < LOGO_SLOW) {
	    last_option_char = 0;
	    logo_speed_y = LOGO_SLOW;
	  }
	}
	break;
      case 'h':
	hide_scroller_text = 1;
	break;
      case 'n':
	hide_scroller_text = 0;
	break;
      default:
	break;
      }
    }
    x = ((screen->w - logo->w) >> 1) + sinf(ticks / 1000.0) * 100;
    y = 60 + sinf(logo_tau) * logo_amplitude;
    logo_tau += movementrate * logo_speed_y;
    dest.x = x;
    dest.y = y;
    dest.h = 0;
    dest.w = 0;
    SDL_BlitSurface(logo, NULL, screen, &dest);
    if(fade_out > 0) {
      SDL_FillRect(black_void, NULL, SDL_MapRGBA(black_void->format, 0, 0, 0, fade_out * 255));
      SDL_BlitSurface(black_void, NULL, screen, NULL);
      fade_out += movementrate / (2000 / 50);
      if(fade_out > 1) {
	running = 0;
	fade_out = 1;
      }
    } else if(fade_out < 0) {
      SDL_FillRect(black_void, NULL, SDL_MapRGBA(black_void->format, 0, 0, 0, -fade_out * 255));
      SDL_BlitSurface(black_void, NULL, screen, NULL);
      fade_out += movementrate / (2700 / 50);
      if(fade_out > 0) {
	fade_out = 0;
      }
    }
    SDL_Flip(screen);
    SDL_Delay(50);
    while(SDL_PollEvent(&event) != 0) {
      if(event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_KEYDOWN) {
	fade_out = 1e-20;
	Mix_FadeOutMusic(2000);
      }
    }
    last_ticks = ticks;
  }
  return 0;
 sdl_error:
  fprintf(stderr, "SDL Error: %s\n", SDL_GetError());
  return 1;  
}
